#!/bin/bash

# mkdebusb.sh (Persistent Debian live environment scripts) 0.0.8
#
# Currently only x86_64 is supported
#
# License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
# This is free software: you are free to change and redistribute it.
# There is NO WARRANTY, to the extent permitted by law.

# initialize all option variables
dev=
iso=
overlay=
dependencies="syslinux grub-efi-amd64-bin"

abort() {
	usage
	printf '\n%s\n' "$1" >&2
	exit 1
}

usage() {
	echo "Usage: mkdebusb.sh -d|--device=DEVICE_NODE -i|--iso=ISO_IMAGE [-o|--overlay=OVERLAY_ARCHIVE]
Example: mkdebusb.sh -d /dev/sdb -i /path/to/Debian/live.iso"
}

show_help_overlay() {
	echo "To create an overlay archive of an already existent persistent Debian live device,
simply mount the partition and tar cvzf the rw folder to a destination of choice.
This archive can then be specified with the -o option during creation of a live media"
}

show_help() {
	usage
	echo "Creates a bootable USB device from a Debian live iso and enables persistence.

Mandatory arguments to long options are mandatory for short options too.
  -d	--device=PATH		full path to the USB device node. 
  -h	--help			display this help and exit
  -i	--iso=PATH		path to the original Debian live iso.
  -o	--overlay=PATH		a backup of an overlay tar.gz archive
  				can be specified which will be
				extracted to the persistence 
				partition. (optional)
				See -b for an explanation on how
				to create an overlay archive.
	--install-dependencies	install the required dependencies
                                only needs to be run once
	--debug			print debug output
                                only needs to be run once
  -v	--version		show version & licence and exit

WARNING: all data on the given device node will be destroyed!"
}

show_version() {
	echo "mkdebusb.sh (Persistent Debian live environment scripts) 0.0.8

License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law."
}


# parse arguments
while :; do
        case $1 in
                -h|--help)
                        show_help
                        exit 0
                        ;;
		-b|--backup)
                        show_help_overlay
                        exit 0
                        ;;

                -d|--device)
                        if [ "$2" ]
                        then
                                dev="$2"
                                shift
                        fi
                        ;;
                --device=?*)
                        dev="${1#*=}"
                        ;;
                --device=)
                        abort '"--directory" must specify a path; see --help'
                        ;;
                -i|--iso)
                        if [ "$2" ]
                        then
                                iso="$2"
                                shift
                        fi
                        ;;
                --iso=?*)
                        iso="${1#*=}"
                        ;;
                --iso=)
                        abort '"--iso" must specify a path; see --help'
                        ;;
                -o|--overlay)
                        if [ "$2" ]
                        then
                                overlay="$2"
                                shift
                        fi
                        ;;
                --overlay=?*)
                        overlay="${1#*=}"
                        ;;
                --overlay=)
                        abort '"--overlay" must specify a path; see --help'
                        ;;
		--install-dependencies)
                        installdeps=true
                        ;;
                --debug)
                        debug=true
                        ;;
                -u|--usage)
                        usage
			exit 0
                        ;;
                -v|--version)
                        show_version
                        exit 0
                        ;;
                --)
                        shift
                        break
                        ;;
                -?*)
                        abort 'Unknown option. See "--help" for help or "--usage" for usage.'
                        ;;
                *)
                        break
        esac

        shift
done


confirmation() {
       	read -n1 -rsp $'WARNING! All data on the specified device will be destroyed. Press any key to continue or Ctrl+C to exit...\n'
}

create_dirs() {
	mkdir /tmp/usb-efi /tmp/usb-live /tmp/usb-persistence /tmp/live-iso
	return 0
}

remove_dirs() {
	 rmdir --ignore-fail-on-non-empty /tmp/usb-efi /tmp/usb-live /tmp/usb-persistence /tmp/live-iso
}

umount_dev() {
	umount "$dev"*
	return 0
}

create_partitions() {
	parted "$dev" --script mktable gpt
	parted "$dev" --script mkpart EFI fat16 1MiB 10MiB
	parted "$dev" --script mkpart live fat16 10MiB 3GiB
	parted "$dev" --script mkpart persistence ext4 3GiB 100%
	parted "$dev" --script set 1 msftdata on
	parted "$dev" --script set 2 legacy_boot on
	parted "$dev" --script set 2 msftdata on
	sync
	return 0
}

init_filesystems() {
	mkfs.vfat -n EFI "$dev"1
	mkfs.vfat -n LIVE "$dev"2
	mkfs.ext4 -F -L persistence "$dev"3
}

mount_filesystems() {
	mount "$dev"1 /tmp/usb-efi
	mount "$dev"2 /tmp/usb-live
	mount "$dev"3 /tmp/usb-persistence
	mount -oro "$iso" /tmp/live-iso
	return 0
}

unmount_filesystems() {
	umount /tmp/usb-efi /tmp/usb-live /tmp/usb-persistence /tmp/live-iso
}

copy_iso_content() {
	cp -ar /tmp/live-iso/* /tmp/usb-live
}

install_bootloader() {
	grub-install --no-uefi-secure-boot --removable --target=x86_64-efi --boot-directory=/tmp/usb-live/boot/ --efi-directory=/tmp/usb-efi "$dev"
	dd bs=440 count=1 conv=notrunc if=/usr/lib/syslinux/mbr/gptmbr.bin of="$dev"
	syslinux --install "$dev"2
	mv /tmp/usb-live/isolinux /tmp/usb-live/syslinux
	mv /tmp/usb-live/syslinux/isolinux.bin /tmp/usb-live/syslinux/syslinux.bin
	mv /tmp/usb-live/syslinux/isolinux.cfg /tmp/usb-live/syslinux/syslinux.cfg
}

configure_persistence() {
	echo "/ union" > /tmp/usb-persistence/persistence.conf
	sed --in-place 's#isolinux/splash#syslinux/splash#' /tmp/usb-live/boot/grub/grub.cfg
	sed --in-place '0,/boot=live/{s/\(boot=live .*\)$/\1 persistence persistence-storage=filesystem persistence-media=removable-usb/}' /tmp/usb-live/boot/grub/grub.cfg /tmp/usb-live/syslinux/menu.cfg
	# action item: make this configurable in the future
	sed --in-place '0,/boot=live/{s/\(boot=live .*\)$/\1 keyboard-layouts=ch locales=de_CH.UTF-8/}' /tmp/usb-live/boot/grub/grub.cfg /tmp/usb-live/syslinux/menu.cfg
}

extract_overlay() {
	if [ "$overlay" ]
	then
		tar xvzf "$overlay" -C /tmp/usb-persistence/
	fi
}

install_dependencies() {
	if [ "$installdeps" = true ]
	then
		echo "Installing dependencies..."
		apt-get update
		apt-get install -y $dependencies \
			&& echo "Dependencies installed successfully" \
			|| abort 'Installation failed!'
		exit 0
	fi
}

spin()
{
	spinner="/|\\-/|\\-"
	while :
	do
		for i in `seq 0 7`
		do
			echo -n "${spinner:$i:1}"
			echo -en "\010"
			sleep 1
		done
	done
}

# make sure we are root & good to go
if [ "$EUID" -ne 0 ]
then
	abort 'Missing root privileges. Aborting.'
fi

install_dependencies

if [ -z "$dev" ]
then
	abort 'Error No device node specified!'
fi

if [ -z "$iso" ]
then
	abort 'Error: No iso image specified!'
fi

confirmation
if [[ ! $debug ]]
then
	# Start the Spinner:
	spin &
	# Make a note of its Process ID (PID):
	SPIN_PID=$!
fi
exec 3>&1; if [[ ! $debug ]]; then exec >/dev/null 2>&1; fi
umount_dev &&
create_partitions &&
init_filesystems &&
create_dirs &&
mount_filesystems &&
copy_iso_content &&
install_bootloader &&
configure_persistence &&
extract_overlay &&
unmount_filesystems &&
remove_dirs
if [[ ! $debug ]]; then kill $SPIN_PID; fi
printf 'Finished\n' >&3
